package com.wg.db.repository;

import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wg.db.repository.config.BaseConfigRepository;
import com.wg.db.repository.entity.IEntityRepository;
import com.wg.db.repository.log.BaseLogRepository;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 仓库管理器，统一管理实体仓库、日志仓库和配置仓库的生命周期（初始化、刷新、关闭）
 * 支持依赖注入、延迟加载和优雅关闭，确保资源安全管理
 *
 * @author 少爷123
 */
public class RepositoryMgr {
    private static final Log log = LogFactory.get();

    /**
     * 最大等待次数（用于关闭时轮询未完成的数据写入）
     */
    private static final int MAX_WAIT_COUNT = 3600;
    /**
     * 实体仓库关闭轮询间隔
     */
    private static final long ENTITY_SHUTDOWN_INTERVAL = 5000L;
    /**
     * 日志仓库关闭轮询间隔
     */
    private static final long LOG_SHUTDOWN_INTERVAL = 2000L;

    /**
     * 仓库实例缓存（类名 -> 仓库对象）
     */
    private final Map<String, Object> repositoryCache = new ConcurrentHashMap<>(256);
    /**
     * 配置仓库缓存（表名 -> 配置仓库）
     */
    private final Map<String, BaseConfigRepository<?, ?>> configRepositories = new ConcurrentHashMap<>(128);
    /**
     * 实体仓库缓存（类名 -> 实体仓库）
     */
    private final Map<String, IEntityRepository<?, ?, ?>> entityRepositories = new ConcurrentHashMap<>(128);
    /**
     * 日志仓库缓存（类名 -> 日志仓库）
     */
    private final Map<String, BaseLogRepository<?>> logRepositories = new ConcurrentHashMap<>(128);

    private RepositoryMgr() {
    }

    /**
     * 获取仓库管理器单例实例
     *
     * @return RepositoryMgr 单例实例
     */
    public static RepositoryMgr getInstance() {
        return Singleton.INSTANCE.getManager();
    }

    /**
     * 初始化所有仓库（配置→日志→实体，确保依赖顺序）
     *
     * @param basePackages 需要扫描的基础包路径列表（如："com.wg.db.repository.entity"）
     */
    public void init(List<String> basePackages) {
        long startTime = System.currentTimeMillis();
        log.info("===== 开始初始化仓库管理器 =====");

        // 1. 优先初始化配置仓库（可能被其他仓库依赖）
        initConfigRepositories(basePackages);
        // 2. 初始化日志仓库
        initLogRepositories(basePackages);
        // 3. 初始化实体仓库
        initEntityRepositories(basePackages);

        long cost = System.currentTimeMillis() - startTime;
        log.info("===== 仓库管理器初始化完成，总耗时：{}ms =====", cost);
    }

    /**
     * 刷新指定表名的配置仓库（重新加载配置数据）
     *
     * @param tableNames 需要刷新的配置表名列表（如："sys_config"）
     */
    public void refreshConfigRepositories(List<String> tableNames) {
        if (tableNames.isEmpty()) {
            return;
        }

        TimeInterval timer = new TimeInterval();
        int successCount = 0;

        for (String tableName : tableNames) {
            BaseConfigRepository<?, ?> configRepo = configRepositories.get(tableName);
            if (configRepo == null) {
                log.warn("未找到配置仓库，表名：{}", tableName);
                continue;
            }

            try {
                configRepo.load();
                successCount++;
                log.info("配置仓库刷新成功，表名：{}", tableName);
            } catch (Exception e) {
                log.error("配置仓库刷新失败，表名：{}，异常：{}", tableName, e.getMessage(), e);
            }
        }

        if (successCount > 0) {
            configLoadAfter(true);
            log.info("配置仓库刷新完成，成功刷新{}个表，耗时：{}ms", successCount, timer.intervalMs());
        } else {
            log.info("配置仓库刷新完成，无需要刷新的表，耗时：{}ms", timer.intervalMs());
        }
    }

    /**
     * 配置仓库加载完成后触发回调（子类可重写实现自定义逻辑）
     *
     * @param reload 是否是重新加载（区别于首次加载）
     */
    protected void configLoadAfter(boolean reload) {
        // 示例：通知其他模块配置已更新
        log.info("配置仓库加载完成，reload={}", reload);
        for (BaseConfigRepository<?, ?> configRepo : configRepositories.values()) {
            configRepo.onLoadComplete(reload);
        }
    }

    /**
     * 初始化配置仓库（扫描指定包下所有BaseConfigRepository子类）
     *
     * @param basePackages 需要扫描的基础包路径列表
     */
    private void initConfigRepositories(List<String> basePackages) {
        TimeInterval timer = new TimeInterval();
        int scannedClasses = 0;
        int createdRepos = 0;

        for (String pack : basePackages) {
            Set<Class<?>> configClasses = ClassUtil.scanPackageBySuper(pack, BaseConfigRepository.class);
            if (configClasses.isEmpty()) {
                continue;
            }

            scannedClasses += configClasses.size();

            for (Class<?> configClazz : configClasses) {
                try {
                    BaseConfigRepository<?, ?> configRepo = (BaseConfigRepository<?, ?>) configClazz.newInstance();
                    String tableName = configRepo.getTableName();
                    configRepositories.put(tableName, configRepo);
                    repositoryCache.put(configClazz.getName(), configRepo);
                    createdRepos++;
                    log.debug("配置仓库实例化成功，类名：{}，表名：{}", configClazz.getName(), tableName);
                } catch (Exception e) {
                    log.error("配置仓库实例化失败，类名：{}，异常：{}", configClazz.getName(), e.getMessage(), e);
                }
            }
        }
        // 触发配置加载完成回调
        configLoadAfter(false);

        log.info("配置仓库初始化完成，扫描包数：{}，发现配置类数：{}，成功创建仓库数：{}，耗时：{}ms",
                basePackages.size(), scannedClasses, createdRepos, timer.intervalMs());
    }

    /**
     * 初始化日志仓库（扫描指定包下所有BaseLogRepository子类）
     *
     * @param basePackages 需要扫描的基础包路径列表
     */
    private void initLogRepositories(List<String> basePackages) {
        TimeInterval timer = new TimeInterval();
        int scannedClasses = 0;
        int createdRepos = 0;

        for (String pack : basePackages) {
            Set<Class<?>> logClasses = ClassUtil.scanPackageBySuper(pack, BaseLogRepository.class);
            if (logClasses.isEmpty()) {
                continue;
            }

            scannedClasses += logClasses.size();

            for (Class<?> logClazz : logClasses) {
                try {
                    BaseLogRepository<?> logRepo = (BaseLogRepository<?>) logClazz.newInstance();
                    logRepositories.put(logClazz.getName(), logRepo);
                    repositoryCache.put(logClazz.getName(), logRepo);
                    createdRepos++;
                    log.debug("日志仓库实例化成功，类名：{}", logClazz.getName());
                } catch (Exception e) {
                    log.error("日志仓库实例化失败，类名：{}，异常：{}", logClazz.getName(), e.getMessage(), e);
                }
            }
        }

        log.info("日志仓库初始化完成，扫描包数：{}，发现日志类数：{}，成功创建仓库数：{}，耗时：{}ms",
                basePackages.size(), scannedClasses, createdRepos, timer.intervalMs());
    }

    /**
     * 初始化实体仓库（扫描指定包下所有IEntityRepository子类）
     *
     * @param basePackages 需要扫描的基础包路径列表
     */
    private void initEntityRepositories(List<String> basePackages) {
        TimeInterval timer = new TimeInterval();
        int scannedClasses = 0;
        int createdRepos = 0;

        for (String pack : basePackages) {
            Set<Class<?>> entityClasses = ClassUtil.scanPackageBySuper(pack, IEntityRepository.class);
            if (entityClasses.isEmpty()) {
                continue;
            }

            scannedClasses += entityClasses.size();

            for (Class<?> entityClazz : entityClasses) {
                try {
                    IEntityRepository<?, ?, ?> entityRepo = (IEntityRepository<?, ?, ?>) entityClazz.newInstance();
                    entityRepositories.put(entityClazz.getName(), entityRepo);
                    repositoryCache.put(entityClazz.getName(), entityRepo);
                    createdRepos++;
                    log.debug("实体仓库实例化成功，类名：{}", entityClazz.getName());
                } catch (Exception e) {
                    log.error("实体仓库实例化失败，类名：{}，异常：{}", entityClazz.getName(), e.getMessage(), e);
                }
            }
        }

        log.info("实体仓库初始化完成，扫描包数：{}，发现实体类数：{}，成功创建仓库数：{}，耗时：{}ms",
                basePackages.size(), scannedClasses, createdRepos, timer.intervalMs());
    }

    /**
     * 根据仓库类获取实例（支持配置、实体、日志仓库）
     *
     * @param repositoryClass 仓库类对象（如：UserRepository.class）
     * @param <T>             仓库类型
     * @return 仓库实例（未找到返回null）
     */
    @SuppressWarnings("unchecked")
    public <T> T getRepository(Class<T> repositoryClass) {
        if (repositoryClass == null) {
            log.warn("获取仓库实例失败：仓库类对象为空");
            return null;
        }

        String className = repositoryClass.getName();
        T repository = (T) repositoryCache.get(className);

        // 延迟加载配置仓库（首次使用时初始化）
        if (repository == null && BaseConfigRepository.class.isAssignableFrom(repositoryClass)) {
            repository = (T) loadConfigRepository(repositoryClass);
        }

        return repository;
    }

    /**
     * 延迟加载配置仓库（按需初始化）
     *
     * @param configClazz 配置仓库类对象
     * @param <T>         配置仓库类型
     * @return 配置仓库实例
     */
    private <T> Object loadConfigRepository(Class<T> configClazz) {
        try {
            BaseConfigRepository<?, ?> configRepo = (BaseConfigRepository<?, ?>) configClazz.newInstance();
            String tableName = configRepo.getTableName();
            configRepositories.put(tableName, configRepo);
            repositoryCache.put(configClazz.getName(), configRepo);
            configRepo.load();
            log.info("延迟加载配置仓库成功，类名：{}，表名：{}", configClazz.getName(), tableName);
            return configRepo;
        } catch (Exception e) {
            log.error("延迟加载配置仓库失败，类名：{}，异常：{}", configClazz.getName(), e.getMessage(), e);
            return null;
        }
    }

    /**
     * 优雅关闭所有仓库（先关闭实体仓库，再关闭日志仓库）
     */
    public void shutdown() {
        log.info("===== 开始关闭仓库管理器 =====");

        // 1. 关闭实体仓库（可能有未写入的数据）
        shutdownEntityRepositories();
        // 2. 关闭日志仓库（可能有未刷盘的数据）
        shutdownLogRepositories();

        log.info("===== 仓库管理器关闭完成 =====");
    }

    /**
     * 关闭所有实体仓库（等待数据写入完成）
     */
    private void shutdownEntityRepositories() {
        if (entityRepositories.isEmpty()) {
            log.info("无实体仓库需要关闭");
            return;
        }

        log.info("===== 开始关闭实体仓库 =====");
        int totalPending = 0;

        // 调用所有实体仓库的shutdown方法（停止接收新数据）
        for (IEntityRepository<?, ?, ?> entityRepo : entityRepositories.values()) {
            try {
                entityRepo.shutdown();
                log.debug("实体仓库已停止接收新数据，类名：{}", entityRepo.getClass().getName());
            } catch (Exception e) {
                log.error("实体仓库关闭失败（停止接收新数据），类名：{}，异常：{}",
                        entityRepo.getClass().getName(), e.getMessage(), e);
            }
        }

        // 轮询检查未写入的数据（最多等待MAX_WAIT_COUNT次）
        for (int i = 0; i < MAX_WAIT_COUNT; i++) {
            totalPending = 0;
            for (IEntityRepository<?, ?, ?> entityRepo : entityRepositories.values()) {
                try {
                    int pendingCount = entityRepo.pendingSize(); // 获取未写入数据量
                    if (pendingCount > 0) {
                        totalPending += pendingCount;
                        log.warn("实体仓库仍有未写入数据，类名：{}，剩余数量：{}",
                                entityRepo.getClass().getName(), pendingCount);
                    }
                } catch (Exception e) {
                    log.error("获取实体仓库未写入数据量失败，类名：{}，异常：{}",
                            entityRepo.getClass().getName(), e.getMessage(), e);
                }
            }

            if (totalPending == 0) {
                log.info("所有实体仓库数据已写入完成");
                break;
            }

            log.info("仍有{}条数据未写入，等待{}ms后重试...", totalPending, ENTITY_SHUTDOWN_INTERVAL);
            ThreadUtil.sleep(ENTITY_SHUTDOWN_INTERVAL, TimeUnit.MILLISECONDS);
        }

        if (totalPending > 0) {
            log.error("实体仓库关闭超时，仍有{}条数据未写入", totalPending);
        }
    }

    /**
     * 关闭所有日志仓库（等待日志刷盘完成）
     */
    private void shutdownLogRepositories() {
        if (logRepositories.isEmpty()) {
            log.info("无日志仓库需要关闭");
            return;
        }

        log.info("===== 开始关闭日志仓库 =====");
        int totalPending = 0;

        // 调用所有日志仓库的shutdownAndFlush方法（停止接收新日志并刷盘）
        for (BaseLogRepository<?> logRepo : logRepositories.values()) {
            try {
                logRepo.shutdownAndFlush();
                log.debug("日志仓库已停止接收新日志并刷盘，类名：{}", logRepo.getClass().getName());
            } catch (Exception e) {
                log.error("日志仓库关闭失败（停止接收新日志并刷盘），类名：{}，异常：{}",
                        logRepo.getClass().getName(), e.getMessage(), e);
            }
        }

        // 轮询检查未刷盘的日志
        for (int i = 0; i < MAX_WAIT_COUNT; i++) {
            totalPending = 0;
            for (BaseLogRepository<?> logRepo : logRepositories.values()) {
                try {
                    int pendingCount = logRepo.getPendingCount(); // 获取未刷盘日志量
                    if (pendingCount > 0) {
                        totalPending += pendingCount;
                        log.warn("日志仓库仍有未刷盘日志，类名：{}，剩余数量：{}",
                                logRepo.getClass().getName(), pendingCount);
                    }
                } catch (Exception e) {
                    log.error("获取日志仓库未刷盘日志量失败，类名：{}，异常：{}",
                            logRepo.getClass().getName(), e.getMessage(), e);
                }
            }

            if (totalPending == 0) {
                log.info("所有日志仓库日志已刷盘完成");
                break;
            }

            log.info("仍有{}条日志未刷盘，等待{}ms后重试...", totalPending, LOG_SHUTDOWN_INTERVAL);
            ThreadUtil.sleep(LOG_SHUTDOWN_INTERVAL, TimeUnit.MILLISECONDS);
        }

        if (totalPending > 0) {
            log.error("日志仓库关闭超时，仍有{}条日志未刷盘", totalPending);
        }
    }

    /**
     * 单例枚举（线程安全）
     */
    private enum Singleton {
        INSTANCE;

        private final RepositoryMgr manager;

        Singleton() {
            this.manager = new RepositoryMgr();
        }

        public RepositoryMgr getManager() {
            return manager;
        }
    }
}